# frontend.py
import tkinter as tk
from tkinter import ttk, messagebox  # 导入 Tkinter 相关模块
import requests  # 用于发送 HTTP 请求
import json  # 用于处理 JSON 数据
from datetime import datetime  # 用于处理日期时间

# --- 配置 ---
# 确保这个 URL 与你的 Flask API 运行的地址匹配
API_BASE_URL = "http://localhost:8080/api/v1"
# 与 API 返回的 ISO 格式匹配的日期格式字符串
DATE_FORMAT = "%Y-%m-%dT%H:%M:%S.%fZ"

# --- API 交互函数 ---


def show_error(title, message):
    """显示一个错误消息框"""
    messagebox.showerror(title, message)


def fetch_tasks():
    """从 API 获取所有任务"""
    try:
        response = requests.get(f"{API_BASE_URL}/tasks")
        response.raise_for_status()  # 如果状态码是 4xx 或 5xx，则抛出异常
        return response.json()
    except requests.exceptions.RequestException as e:
        show_error("API 错误", f"无法连接到 API 或获取任务:\n{e}")
        return None
    except json.JSONDecodeError:
        show_error("API 错误", "从 API 接收到无效的数据格式。")
        return None


def fetch_task_details(task_id):
    """获取指定任务的详细信息"""
    try:
        response = requests.get(f"{API_BASE_URL}/tasks/{task_id}")
        response.raise_for_status()
        return response.json()
    except requests.exceptions.RequestException as e:
        show_error("API 错误", f"无法获取任务 ID {task_id} 的详细信息:\n{e}")
        return None
    except json.JSONDecodeError:
        show_error("API 错误", "接收到无效的任务详细信息数据格式。")
        return None


def create_task_api(task_data):
    """发送 POST 请求来创建一个新任务"""
    try:
        headers = {"Content-Type": "application/json"}
        # 发送前过滤掉可选字段的空字符串
        payload = {k: v for k, v in task_data.items() if v is not None and v != ""}
        response = requests.post(f"{API_BASE_URL}/tasks", headers=headers, json=payload)
        response.raise_for_status()
        return response.json()
    except requests.exceptions.RequestException as e:
        error_detail = ""
        try:
            # 尝试从响应中获取更详细的错误信息
            error_detail = response.json().get("details", str(e))
        except:  # 处理响应不是 JSON 或没有 'details' 键的情况
            error_detail = str(e)
        show_error("API 错误", f"创建任务失败:\n{error_detail}")
        return None
    except json.JSONDecodeError:
        show_error("API 错误", "创建任务后接收到无效的数据格式。")
        return None


def update_task_api(task_id, task_data):
    """发送 PUT 请求来更新一个现有任务"""
    try:
        headers = {"Content-Type": "application/json"}
        # 发送前过滤掉值为 None 的字段，允许空字符串用于更新
        payload = {k: v for k, v in task_data.items() if v is not None}
        response = requests.put(
            f"{API_BASE_URL}/tasks/{task_id}", headers=headers, json=payload
        )
        response.raise_for_status()
        return response.json()
    except requests.exceptions.RequestException as e:
        error_detail = ""
        try:
            error_detail = response.json().get("details", str(e))
        except:
            error_detail = str(e)
        show_error("API 错误", f"更新任务 ID {task_id} 失败:\n{error_detail}")
        return None
    except json.JSONDecodeError:
        show_error("API 错误", "更新任务后接收到无效的数据格式。")
        return None


def delete_task_api(task_id):
    """发送 DELETE 请求来移除一个任务"""
    try:
        response = requests.delete(f"{API_BASE_URL}/tasks/{task_id}")
        response.raise_for_status()
        return True  # 成功由 204 No Content 或其他 2xx 状态码表示
    except requests.exceptions.RequestException as e:
        show_error("API 错误", f"删除任务 ID {task_id} 失败:\n{e}")
        return False


# --- Tkinter 应用类 ---


class TaskManagerApp:
    def __init__(self, root):
        self.root = root
        self.root.title("任务管理器")
        # self.root.geometry("700x500") # 可选：设置初始窗口大小

        # 尝试使用主题
        try:
            from ttkthemes import ThemedTk

            if isinstance(self.root, ThemedTk):  # 检查 root 是否已经是 ThemedTk 实例
                self.root.set_theme(
                    "arc"
                )  # 示例主题, 也可以尝试 'plastik', 'adapta' 等
        except ImportError:
            print("未安装 ttkthemes，使用默认 ttk 样式。")
            pass  # 如果 ttkthemes 不可用，则回退到默认 ttk 样式

        self.tasks_data = {}  # 用字典存储完整的任务数据，键是任务 ID
        self.selected_task_id = None  # 当前选中的任务 ID

        # --- 布局 ---
        # 主框架
        main_frame = ttk.Frame(root, padding="10")
        main_frame.grid(
            row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S)
        )  # 填充整个窗口
        root.columnconfigure(0, weight=1)  # 让主框架的列可伸缩
        root.rowconfigure(0, weight=1)  # 让主框架的行可伸缩

        # 左侧：任务列表
        list_frame = ttk.LabelFrame(main_frame, text="任务列表", padding="5")
        list_frame.grid(
            row=0, column=0, padx=(0, 10), pady=5, sticky=(tk.N, tk.S, tk.W, tk.E)
        )  # 左侧，垂直填充
        main_frame.columnconfigure(0, weight=1)  # 允许列表框架横向扩展一点
        main_frame.rowconfigure(0, weight=1)  # 允许列表框架纵向扩展

        # 任务列表框
        self.task_listbox = tk.Listbox(
            list_frame, height=15, width=30, exportselection=False
        )  # exportselection=False 防止焦点移开时取消选择
        self.task_listbox.pack(
            side=tk.LEFT, fill=tk.BOTH, expand=True
        )  # 填充 list_frame
        self.task_listbox.bind("<<ListboxSelect>>", self.on_task_select)  # 绑定选择事件

        # 滚动条
        scrollbar = ttk.Scrollbar(
            list_frame, orient=tk.VERTICAL, command=self.task_listbox.yview
        )
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)  # 放在列表框右侧，垂直填充
        self.task_listbox.config(yscrollcommand=scrollbar.set)  # 列表框滚动时更新滚动条

        # 右侧：详细信息和控制按钮
        details_frame = ttk.Frame(main_frame, padding="5")
        details_frame.grid(
            row=0, column=1, pady=5, sticky=(tk.N, tk.S, tk.W, tk.E)
        )  # 右侧，垂直填充
        main_frame.columnconfigure(1, weight=2)  # 给详细信息区域更多横向权重

        # 表单字段框架
        form_frame = ttk.LabelFrame(details_frame, text="任务详情", padding="10")
        form_frame.grid(
            row=0, column=0, columnspan=2, sticky=(tk.W, tk.E)
        )  # 顶部，横向填充
        details_frame.columnconfigure(0, weight=1)  # 允许表单框架横向扩展

        # 表单字段 (标签 + 输入框/标签)
        ttk.Label(form_frame, text="ID:").grid(row=0, column=0, sticky=tk.W, pady=2)
        self.id_label = ttk.Label(form_frame, text="-")  # 用于显示 ID 的标签
        self.id_label.grid(row=0, column=1, sticky=tk.W, pady=2)

        ttk.Label(form_frame, text="标题:").grid(row=1, column=0, sticky=tk.W, pady=2)
        self.title_var = tk.StringVar()  # Tkinter 字符串变量，用于绑定输入框
        self.title_entry = ttk.Entry(form_frame, textvariable=self.title_var, width=40)
        self.title_entry.grid(row=1, column=1, sticky=(tk.W, tk.E), pady=2)  # 横向填充

        ttk.Label(form_frame, text="描述:").grid(row=2, column=0, sticky=tk.W, pady=2)
        self.desc_var = tk.StringVar()
        self.desc_entry = ttk.Entry(form_frame, textvariable=self.desc_var, width=40)
        self.desc_entry.grid(row=2, column=1, sticky=(tk.W, tk.E), pady=2)

        ttk.Label(form_frame, text="状态:").grid(row=3, column=0, sticky=tk.W, pady=2)
        self.status_var = tk.StringVar()
        self.status_combobox = ttk.Combobox(
            form_frame,
            textvariable=self.status_var,
            values=["todo", "in_progress", "done"],
            state="readonly",
        )  # readonly 防止输入自定义值
        self.status_combobox.grid(row=3, column=1, sticky=(tk.W, tk.E), pady=2)

        ttk.Label(form_frame, text="截止日期:").grid(
            row=4, column=0, sticky=tk.W, pady=2
        )
        self.due_date_var = tk.StringVar()
        self.due_date_entry = ttk.Entry(
            form_frame, textvariable=self.due_date_var, width=40
        )
        self.due_date_entry.grid(row=4, column=1, sticky=(tk.W, tk.E), pady=2)
        # 提示日期格式
        ttk.Label(form_frame, text=f"(可选, 格式: {DATE_FORMAT})").grid(
            row=5, column=1, sticky=tk.W, pady=0, padx=0, columnspan=2
        )

        ttk.Label(form_frame, text="创建时间:").grid(
            row=6, column=0, sticky=tk.W, pady=2
        )
        self.created_label = ttk.Label(form_frame, text="-")
        self.created_label.grid(row=6, column=1, sticky=tk.W, pady=2)

        ttk.Label(form_frame, text="更新时间:").grid(
            row=7, column=0, sticky=tk.W, pady=2
        )
        self.updated_label = ttk.Label(form_frame, text="-")
        self.updated_label.grid(row=7, column=1, sticky=tk.W, pady=2)

        form_frame.columnconfigure(1, weight=1)  # 让输入框列可伸缩

        # 操作按钮框架
        button_frame = ttk.Frame(details_frame, padding="5")
        button_frame.grid(
            row=1, column=0, columnspan=2, pady=10, sticky=(tk.W, tk.E)
        )  # 表单下方，横向填充

        # 按钮
        self.refresh_button = ttk.Button(
            button_frame, text="刷新列表", command=self.load_tasks
        )
        self.refresh_button.pack(side=tk.LEFT, padx=5)

        self.new_button = ttk.Button(
            button_frame, text="新建任务", command=self.prepare_new_task
        )
        self.new_button.pack(side=tk.LEFT, padx=5)

        self.save_button = ttk.Button(
            button_frame, text="保存任务", command=self.save_task, state=tk.DISABLED
        )  # 初始禁用
        self.save_button.pack(side=tk.LEFT, padx=5)

        self.delete_button = ttk.Button(
            button_frame, text="删除任务", command=self.delete_task, state=tk.DISABLED
        )  # 初始禁用
        self.delete_button.pack(side=tk.LEFT, padx=5)

        # 状态栏
        self.status_var_label = tk.StringVar()  # 用于显示状态信息的变量
        self.status_bar = ttk.Label(
            main_frame,
            textvariable=self.status_var_label,
            relief=tk.SUNKEN,
            anchor=tk.W,
            padding="2",
        )
        self.status_bar.grid(
            row=1, column=0, columnspan=2, sticky=(tk.W, tk.E)
        )  # 底部，横向填充
        self.show_status("准备就绪。请确保 RESTful API 正在运行。")

        # --- 初始加载 ---
        self.load_tasks()  # 程序启动时加载任务列表

    def show_status(self, message):
        """更新状态栏的文本"""
        self.status_var_label.set(message)
        # print(message) # 也可以打印到控制台方便调试

    def clear_form(self, clear_id=True):
        """清空详细信息表单的字段"""
        if clear_id:
            self.id_label.config(text="-")
        self.title_var.set("")
        self.desc_var.set("")
        self.status_var.set("")  # 清空下拉框选择
        self.due_date_var.set("")
        self.created_label.config(text="-")
        self.updated_label.config(text="-")
        self.title_entry.focus()  # 将焦点设置到标题输入框

    def load_tasks(self):
        """从 API 获取任务并填充到列表框中"""
        self.show_status("正在加载任务...")
        self.task_listbox.delete(0, tk.END)  # 清空现有列表项
        self.tasks_data.clear()  # 清空缓存的任务数据
        self.selected_task_id = None  # 清除当前选择
        self.clear_form()  # 清空表单
        self.save_button.config(state=tk.DISABLED)  # 禁用保存按钮
        self.delete_button.config(state=tk.DISABLED)  # 禁用删除按钮

        tasks = fetch_tasks()  # 调用 API 函数获取数据
        if tasks is not None:
            # 按 ID 排序任务，使列表顺序稳定 (可选)
            tasks.sort(key=lambda t: t.get("id", 0))
            for task in tasks:
                task_id = task.get("id")
                title = task.get("title", "无标题")  # 处理可能没有标题的情况
                if task_id is not None:
                    # 格式化列表框中显示的文本
                    display_text = f"{task_id}: {title}"
                    self.task_listbox.insert(tk.END, display_text)
                    self.tasks_data[task_id] = task  # 将完整的任务数据存入字典缓存
            self.show_status(f"已加载 {len(tasks)} 个任务。")
        else:
            self.show_status("加载任务失败。请检查 API 连接。")

    def on_task_select(self, event=None):
        """处理任务列表框中的选择变化事件"""
        selection = self.task_listbox.curselection()  # 获取当前选中的项的索引
        if not selection:  # 如果没有选中项
            self.selected_task_id = None
            self.clear_form()
            self.save_button.config(text="保存任务", state=tk.DISABLED)
            self.delete_button.config(state=tk.DISABLED)
            return

        selected_index = selection[0]
        selected_text = self.task_listbox.get(selected_index)  # 获取选中项的文本

        try:
            # 从列表框文本中提取 ID (假设格式为 "ID: Title")
            task_id_str = selected_text.split(":", 1)[0]
            self.selected_task_id = int(task_id_str)
        except (IndexError, ValueError):
            show_error("选择错误", "无法从所选项中确定任务 ID。")
            self.selected_task_id = None
            self.clear_form()
            self.save_button.config(state=tk.DISABLED)
            self.delete_button.config(state=tk.DISABLED)
            return

        # 获取并显示完整的任务详情 (优先使用缓存数据)
        task = self.tasks_data.get(self.selected_task_id)
        if task:
            self.populate_form(task)  # 使用缓存数据填充表单
            self.save_button.config(
                text="更新任务", state=tk.NORMAL
            )  # 启用保存按钮 (此时为更新操作)
            self.delete_button.config(state=tk.NORMAL)  # 启用删除按钮
            self.show_status(f"已选择任务 ID: {self.selected_task_id}")
        else:
            # 如果缓存中没有 (可能缓存过时了)，尝试直接从 API 获取
            self.show_status(f"正在获取任务 ID: {self.selected_task_id} 的详情...")
            task_details = fetch_task_details(self.selected_task_id)
            if task_details:
                self.tasks_data[self.selected_task_id] = task_details  # 更新缓存
                self.populate_form(task_details)  # 填充表单
                self.save_button.config(text="更新任务", state=tk.NORMAL)
                self.delete_button.config(state=tk.NORMAL)
                self.show_status(f"已选择任务 ID: {self.selected_task_id}")
            else:
                # 如果 API 也获取失败
                self.show_status(f"无法加载任务 ID: {self.selected_task_id} 的详情。")
                self.selected_task_id = None
                self.clear_form()
                self.save_button.config(state=tk.DISABLED)
                self.delete_button.config(state=tk.DISABLED)

    def populate_form(self, task):
        """使用任务字典中的数据填充表单字段"""
        self.id_label.config(text=str(task.get("id", "-")))
        self.title_var.set(task.get("title", ""))
        self.desc_var.set(task.get("description", ""))
        self.status_var.set(task.get("status", ""))
        # 处理 due_date 可能为 None 的情况，显示空字符串
        self.due_date_var.set(task.get("due_date", "") or "")
        # 格式化日期时间显示
        self.created_label.config(text=self.format_datetime(task.get("created_at")))
        self.updated_label.config(text=self.format_datetime(task.get("updated_at")))

    def format_datetime(self, dt_string):
        """格式化 ISO 日期时间字符串以便显示，处理 None 值"""
        if not dt_string:
            return "-"
        try:
            # 解析 ISO 8601 字符串
            dt_obj = datetime.strptime(dt_string, DATE_FORMAT)
            # 格式化为更易读的格式 (可根据需要调整格式)
            return dt_obj.strftime("%Y-%m-%d %H:%M:%S")
        except (ValueError, TypeError):
            # 如果解析失败，返回原始字符串
            return dt_string

    def prepare_new_task(self):
        """清空表单并准备创建新任务"""
        self.task_listbox.selection_clear(0, tk.END)  # 取消列表框中的任何选择
        self.selected_task_id = None  # 清除选中的 ID
        self.clear_form()  # 清空表单
        self.status_var.set("todo")  # 为新任务设置默认状态
        self.save_button.config(
            text="创建任务", state=tk.NORMAL
        )  # 启用保存按钮 (此时为创建操作)
        self.delete_button.config(state=tk.DISABLED)  # 禁用删除按钮
        self.show_status("请输入新任务的详细信息。")

    def save_task(self):
        """处理保存任务（创建或更新）"""
        title = self.title_var.get().strip()  # 获取标题并去除首尾空格
        if not title:
            show_error("验证错误", "任务标题不能为空。")
            self.title_entry.focus()  # 让用户方便修改
            return

        # 准备要发送到 API 的数据
        task_data = {
            "title": title,
            "description": self.desc_var.get().strip(),
            "status": self.status_var.get() or "todo",  # 如果状态为空，默认为 'todo'
            "due_date": self.due_date_var.get().strip()
            or None,  # 如果截止日期为空，发送 None
        }

        # 粗略验证截止日期格式 (真正的验证由 API 完成)
        if task_data["due_date"]:
            try:
                # 尝试按我们的格式解析，如果失败则警告
                datetime.strptime(task_data["due_date"], DATE_FORMAT)
            except ValueError:
                # 更友好的方式可能是使用日期选择器控件
                if not messagebox.askyesno(
                    "日期格式警告",
                    f"截止日期 '{task_data['due_date']}' 似乎不符合预期的格式 ({DATE_FORMAT})。\n\n确定要继续吗？ (API 可能会拒绝)",
                    icon="warning",
                ):
                    self.due_date_entry.focus()  # 让用户修改日期
                    return

        if self.selected_task_id is None:  # 如果没有选中 ID，说明是创建新任务
            self.show_status("正在创建新任务...")
            created_task = create_task_api(task_data)  # 调用创建 API
            if created_task:
                self.show_status(
                    f"任务 '{created_task.get('title')}' 创建成功 (ID: {created_task.get('id')})。"
                )
                self.load_tasks()  # 刷新列表以显示新任务
                # 可选：自动选中新创建的任务
                # self.select_task_in_listbox(created_task.get('id'))
            else:
                self.show_status("创建任务失败。")
                # 保留表单内容让用户可以修正

        else:  # 如果有选中 ID，说明是更新现有任务
            self.show_status(f"正在更新任务 ID: {self.selected_task_id}...")
            updated_task = update_task_api(
                self.selected_task_id, task_data
            )  # 调用更新 API
            if updated_task:
                self.show_status(f"任务 ID {self.selected_task_id} 更新成功。")
                # 直接更新缓存和列表框中的项 (比完全重新加载更高效)
                self.tasks_data[self.selected_task_id] = updated_task
                # 找到列表框中的对应项并更新其文本
                for i in range(self.task_listbox.size()):
                    text = self.task_listbox.get(i)
                    if text.startswith(f"{self.selected_task_id}:"):
                        self.task_listbox.delete(i)
                        self.task_listbox.insert(
                            i, f"{updated_task['id']}: {updated_task['title']}"
                        )
                        self.task_listbox.selection_set(i)  # 保持选中状态
                        break
                self.populate_form(
                    updated_task
                )  # 用最新的数据刷新表单 (例如更新时间戳)

            else:
                self.show_status(f"更新任务 ID: {self.selected_task_id} 失败。")
                # 保留表单内容

    def delete_task(self):
        """处理删除选定任务的操作"""
        if self.selected_task_id is None:
            show_error("选择错误", "没有选中要删除的任务。")
            return

        # 从缓存获取任务标题用于确认对话框
        task_title = self.tasks_data.get(self.selected_task_id, {}).get(
            "title", f"ID {self.selected_task_id}"
        )

        # 弹出确认对话框
        if messagebox.askyesno(
            "确认删除", f"您确定要删除任务:\n'{task_title}' 吗？", icon="warning"
        ):
            self.show_status(f"正在删除任务 ID: {self.selected_task_id}...")
            success = delete_task_api(self.selected_task_id)  # 调用删除 API
            if success:
                self.show_status(f"任务 ID {self.selected_task_id} 删除成功。")
                self.load_tasks()  # 删除后最简单的方式是重新加载整个列表
            else:
                self.show_status(f"删除任务 ID: {self.selected_task_id} 失败。")

    # # 辅助函数：根据 ID 在列表框中选中某一项 (可选)
    # def select_task_in_listbox(self, task_id):
    #     """根据任务 ID 在列表框中选中对应的项"""
    #     for i in range(self.task_listbox.size()):
    #         text = self.task_listbox.get(i)
    #         if text.startswith(f"{task_id}:"):
    #             self.task_listbox.selection_clear(0, tk.END) # 清除旧选择
    #             self.task_listbox.selection_set(i)      # 设置新选择
    #             self.task_listbox.see(i)              # 滚动以确保该项可见
    #             self.on_task_select()                 # 触发选择事件处理逻辑
    #             break


# --- 主程序执行 ---
if __name__ == "__main__":
    # 尝试使用 ThemedTk (如果安装了 ttkthemes)
    try:
        from ttkthemes import ThemedTk

        root = ThemedTk(theme="arc")  # 可以换成其他主题，如 "plastik", "adapta"
    except ImportError:
        root = tk.Tk()  # 如果没有安装 ttkthemes，则使用标准的 Tk

    app = TaskManagerApp(root)  # 创建应用实例
    root.mainloop()  # 启动 Tkinter 事件循环
